from http.server import HTTPServer, BaseHTTPRequestHandler
from http import HTTPStatus, cookies
from struct import pack, unpack
from random import randint
from base64 import b64encode, b64decode
from hashlib import md5, sha1
from urllib.parse import parse_qs
import datetime, html, os, json, threading, ssl, sys

host = ('0.0.0.0', 6911)
enableHttps = False
secret = "b6c571f8b9d34e800ce2840b10095176d64cc6b8" # 假设密码为xxxxxx，则secret = sha1('xxxxxx&Salt=0')
history = 200

filekey = "242a34e7c21fad189e03e56056a17661605dff7b"
filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), md5(filekey.encode()).hexdigest() + ".dat")

page_login = '''<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>访问授权</title>
<style>
    body, input{
        margin: 0px;
        padding: 0px;
        font-size: 100%;
        font: inherit;
    }
    .wrap{
        width: 360px;
        height: 240px;
        position: absolute;
        left:50%;
        top:50%;
        margin-left:-180px;
        margin-top:-120px;
        border-radius: 10px;
        border: 1px solid #858585;
    }
    .line{
        padding: 10px 10px 0px 10px;
        text-align: center;
		color: #666666;
    }
	.title{
        font-size: 120%;
		font-weight: bold;
		line-height: 50px;
	}
    .key{
        width: 238px;
        padding: 10px;
        border: solid 1px #cccccc;
    }
    .checkline{
        padding-left:50px;
		text-align: left;
    }
    .checkline label input{
        vertical-align: middle;
    }
    .checkline label span{
        padding-left:4px;
        font-size: 14px;
        -moz-user-select:none;
        -webkit-user-select:none;
        -ms-user-select:none;
        -khtml-user-select:none;
        user-select:none;
        vertical-align: middle;
    }
    .btn{
        width: 260px;
        padding: 10px;
        margin-top: 10px;
        border-radius: 20px;
        border: solid 1px #cccccc;
        color: #666666;
    }
</style>
</head>
<body>
<div class="wrap">
<form action="/api/login" method="post">
<div class="line title">网络剪贴板</div>
<div class="line"><input class="key" type="password" name="key" minlength="6" maxlength="20" placeholder="输入密钥"></div>
<div class="line checkline"><label><input class="islong" type="checkbox" name="islong"><span>长久授权</span></label></div>
<div class="line"><input class="btn" type="submit" value="授权"></div>
</form>
</div>
</body></html>'''

page_index = '''<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>网络剪贴板</title>
<style>
    body, textarea, ul, li{
        margin: 0px;
        padding: 0px;
        font-size: 100%%;
        font: inherit;
    }
    .wrap{
        position: fixed;
        left: 0px;
        right: 0px;
        top: 0px;
        bottom: 0px;
    }
    .content{
        position: fixed;
        left: 0px;
        right: 0px;
        top: 0px;
        bottom: 66px;
    }
    .btn_bar{
        position: fixed;
        left: 0px;
        right: 0px;
        bottom: 0px;
        height: 60px;
        width: 100%%;
        overflow: hidden;
        user-select: none;
    }
    .btn_left, .btn_right{
        padding: 10px 0px;
        width: 50%%;
        height: 40px;
        line-height: 40px;
        font-size: 20px;
        text-align: center;
        cursor: pointer;
    }
    .btn_left{
        float: left;
    }
    .btn_right{
        float: right;
    }
    .current_content{
        border: none;
        outline: 0;
        display: block;
        -moz-box-sizing: border-box;
        -webkit-box-sizing: border-box;
        box-sizing: border-box;
        width: 100%%;
        resize: none;
        padding: 10px;
        word-break: break-all;
    }
    .current_content:focus{
        border: none;
        outline: 0;
    }
    .history_wrap{
        display: none;
        background-color: #ffffff;
    }
    .history_list{
        overflow-y: scroll;
        padding: 10px;
        list-style: none;
    }
    .history_list li{
        height: 50px;
        line-height: 50px;
        border-bottom: #eeeeee 1px solid;
        cursor: pointer;
    }
    .history_list li span{
        float: left;
        width: 120px;
        text-align: center;
    }
    .history_list li span:first-child{
        width: 40px;
    }
    .history_list li div{
        margin-left: 166px;
        height: 50px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
</style>
</head>

<body>
<div class="wrap">
    <textarea id="current_content" class="content current_content" placeholder="在此处输入或粘贴文本，内容会自动保存。">%s</textarea>
    <div class="btn_bar"><span id="btn_history" class="btn_left">历史记录</span><span id="btn_empty" class="btn_right">清除</span></div>
</div>
<div id="history_wrap" class="wrap history_wrap">
    <ul id="history_list" class="content history_list">
    </ul>
    <div class="btn_bar"><span id="btn_exit" class="btn_left">返回</span><span id="btn_delete" class="btn_right">全部删除</span></div>
</div>
<script>
    function DOM(id)
    {
        return document.getElementById(id);
    }
    function ajaxRequest(data, success){
        var xhr = new XMLHttpRequest();
        xhr.open('post', '/api/clipoard');
        xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xhr.send(data);
        xhr.onreadystatechange = function ()
        {
            if (xhr.readyState == 4)
            {
                if(xhr.status == 200 && xhr.getResponseHeader('content-type').indexOf('application/json') != -1)
                {
                    //console.log(xhr.responseText);
                    try {
                        var result = JSON.parse(xhr.responseText);
                    }
                    catch (err)
                    {
                        console.log("服务器异常");
                        return;
                    }
                    if(result.code === 0)
                    {
                        if(success) success(result.data);
                    }
                    else
                    {
                        console.log("业务错误");
                        console.log(result);
                        if(result.code === 2)
                        {
                            window.location.href = "/login.html";
                        }
                    }
                }
                else
                {
                    console.log("网络异常");
                }
            }
        };
    }
    function debounce(fn, delay){
        let t = null;
        return function(){
            if(t != null)
            {
                clearTimeout(t);
            }
            t = setTimeout(() => {
                fn.call(this);
            }, delay);
        };
    }
    DOM('current_content').oninput = debounce(function(){
        ajaxRequest('action=save&content=' + encodeURIComponent(this.value));
    }, 500);
    DOM('btn_history').onclick = function(){
        ajaxRequest('action=history', function(data){
            //console.log(data);
            var ul_html = data.list.length > 0 ? '' : '历史列表为空！';
            var line = 1;
            for(var item of data.list) {
                ul_html += "<li><span>" + (line++) + "</span>";
                ul_html += "<span>" + item.time.substr(5, 11) + "</span>";
                ul_html += "<div>" + item.content.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</div></li>";
            }
            DOM('history_list').innerHTML = ul_html;
            DOM('history_wrap').style.display = 'block';
            DOM('history_list').scrollTop = 0;
            document.title = '历史记录';
        });
    }
    DOM('btn_empty').onclick = function(){
        ajaxRequest('action=save&content=', function(){
            DOM('current_content').value = '';
        });
    }
    DOM('btn_exit').onclick = function(){
        DOM('history_wrap').style.display = 'none';
        document.title = '网络剪贴板';
    }
    DOM('btn_delete').onclick = function(){
        ajaxRequest('action=delete', function(){
            DOM('history_list').innerHTML = '历史列表为空！';
            DOM('current_content').value = '';
        });
    }
    DOM('history_list').addEventListener('click', function(e)
    {
        var event = e || window.event;
        var target = event.target || event.srcElement;
        var li = target.parentNode;
        if(li.nodeName.toLowerCase() == 'li')
        {
            DOM('current_content').value = li.getElementsByTagName('div')[0].innerHTML.replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&amp;/g, "&");
            DOM('history_wrap').style.display = 'none';
            document.title = '网络剪贴板';
        }
    });
</script>
</body>

</html>'''

def encrypt(data, password):
    iv = pack('>I', randint(0, 4294967295))
    key = md5(password.encode() + iv).digest()
    data_bytes = bytearray(data.encode())
    fill_count = 16 - (len(data_bytes) % 16)
    if fill_count > 1:
        data_bytes += bytes(fill_count -1)
    data_bytes += pack('>b', fill_count)
    data_len = len(data_bytes)
    for i in range((2 * data_len) -1):
        src = i % data_len
        dst = (i + 1) % data_len
        k = i & 15
        data_bytes[dst] = ((data_bytes[src] + data_bytes[dst]) & 255) ^ key[k]
    return b64encode(iv + data_bytes).decode()

def decrypt(data, password):
    data = b64decode(data)
    iv = data[0:4]
    data_bytes = bytearray(data[4:])
    key = md5(password.encode() + iv).digest()
    data_len = len(data_bytes)
    for i in range((2 * data_len) - 2, -1 , -1):
        src = i % data_len
        dst = (i + 1) % data_len
        k = i & 15
        data_bytes[dst] = ((data_bytes[dst] ^ key[k]) + 256 - data_bytes[src]) & 255
    return data_bytes[0:-data_bytes[-1]].decode()

if not os.path.exists(filepath):
    with open(filepath, 'w', encoding = 'ascii') as f:
        f.write(encrypt(json.dumps([], ensure_ascii = False), filekey))
if not os.path.isfile(filepath):
    print("Cannot found data file: %s" % (filepath))
    sys.exit()

class RequestHandler(BaseHTTPRequestHandler):

    timeout = 3

    def version_string(self):
        return "CLIPBOARD v2.0"

    def do_GET(self):

        #请求地址
        path = self.path
        allow_request = ["/", "/index.html", "/index.htm", "/favicon.ico", "/login.html"]
        if path not in allow_request:
            self.send_error(HTTPStatus.NOT_FOUND, "File not found")
            return

        #首页
        if path == "/" or path == "/index.html" or  path == "/index.htm":
            #判断权限
            cookie = cookies.SimpleCookie()
            if self.headers["Cookie"] :
                cookie.load(self.headers["Cookie"])
            if ("timestamp" not in cookie) or ("signature" not in cookie) or sha1((cookie['timestamp'].value + secret).encode()).hexdigest() != cookie['signature'].value.lower():
                self.send_response(302)
                self.send_header("Location", "/login.html")
                self.end_headers()
                return

            #读取剪贴板内容
            content = ""
            if os.path.isfile(filepath):
                with open(filepath, 'r', encoding = 'ascii') as f:
                    filecontent = f.read()
                    if filecontent:
                        saved = json.loads(decrypt(filecontent, filekey))
                        if len(saved) > 0:
                            content = saved[-1]["content"]

            #响应页面内容
            self.send_response(200)
            self.send_header("Content-type", "text/html; charset=utf-8")
            self.end_headers()
            self.wfile.write((page_index % (html.escape(content),)).encode())

        #网站图标
        if path == "/favicon.ico":
            self.send_response(200)
            self.send_header("Content-type", "image/x-icon")
            self.end_headers()
            self.wfile.write(b64decode("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m/+bm5v/m5ub/5ubm/+bm5v/m5ub/5ubm/+bm5v/pKOk//ft8//37fP/R5fh/0eX4f8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAR5fh/0eX4f/37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z//ft8/9Hl+H/R5fh/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHl+H/R5fh//ft8//37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z/0eX4f9Hl+H/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEeX4f9Hl+H/9+3z//ft8/+ko6T/m5ub/5ubm/+bm5v/m5ub/5ubm/+bm5v/m5ub/5ubm/+bm5v/m5ub/5ubm/+bm5v/pKOk//ft8//37fP/R5fh/0eX4f8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAR5fh/0eX4f/37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z//ft8/9Hl+H/R5fh/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHl+H/R5fh//ft8//37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z/0eX4f9Hl+H/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEeX4f9Hl+H/9+3z//ft8/+ko6T/m5ub/5ubm/+bm5v/m5ub/5ubm/+bm5v/m5ub/5ubm/+bm5v/m5ub/5ubm/+bm5v/pKOk//ft8//37fP/R5fh/0eX4f8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAR5fh/0eX4f/37fP/9+3z//ft8//37fP/9+3z//ft8//37fP/9+3z//ft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"))
            return

        #访问授权页面
        if path == "/login.html":
            self.send_response(200)
            self.send_header("Content-type", "text/html; charset=utf-8")
            self.end_headers()
            self.wfile.write(page_login.encode())
            return

    def post_success(self, data = None):
        self.wfile.write(json.dumps(dict(code = 0, msg = "success", data = data), ensure_ascii = False).encode())

    def post_failure(self, code = 1, msg = None, data = None):
        code = int(code)
        if code < 1:
            code = 1
        errorMsg = {
            "ERROR_1" : "系统异常，请稍后再试",
            "ERROR_2" : "没有权限",
            "ERROR_10001" : "参数缺失: “action” ",
            "ERROR_10002" : "参数缺失: “content”",
            "ERROR_10003" : "保存失败",
            "ERROR_10004" : "清空失败",
        }
        if msg is None:
            key = "ERROR_" + str(code)
            msg = errorMsg[key] if key in errorMsg else "未定义的错误码"
        self.wfile.write(json.dumps(dict(code = code, msg = msg, data = data), ensure_ascii = False).encode())

    def clipboard_save(self, content):
        result = False
        file_lock = threading.Lock()
        file_lock.acquire()
        saved = []
        if content is not None:
            with open(filepath, 'r', encoding = 'ascii') as f:
                filecontent = f.read()
                if filecontent:
                    saved = json.loads(decrypt(filecontent, filekey))
            if len(saved) >= history:
                saved.pop(0)
            saved.append(dict(time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), content = content))
        with open(filepath, 'w', encoding = 'ascii') as f:
            f.write(encrypt(json.dumps(saved, ensure_ascii = False), filekey))
            result = True
        file_lock.release()
        return result

    def post_action(self, datas):
        if "action" not in datas:
            return self.post_failure(10001)
        action = datas["action"][0]

        if action == "save":
            if "content" not in datas:
                return self.post_failure(10002)
            if not self.clipboard_save(datas["content"][0]):
                return self.post_failure(10003)
            return self.post_success()

        if action == "history":
            if os.path.isfile(filepath):
                with open(filepath, 'r', encoding = 'ascii') as f:
                    filecontent = f.read()
                    if filecontent:
                        saved = json.loads(decrypt(filecontent, filekey))
                        saved.reverse()
                        return self.post_success({"list" : saved})

        if action == "delete":
            if not self.clipboard_save(None):
                return self.post_failure(10004)
            return self.post_success()

        self.post_failure()

    def do_POST(self):

        #请求地址
        path = self.path
        allow_request = ["/api/login", "/api/clipoard"]
        if path not in allow_request:
            self.send_error(HTTPStatus.NOT_FOUND, "File not found")
            return

        #获取POST提交的数据
        datas = self.rfile.read(int(self.headers['content-length'])).decode()
        datas = parse_qs(datas, True)

        #登录处理
        if path == "/api/login":
            if ("key" not in datas) or sha1((datas["key"][0] + "&Salt=0").encode()).hexdigest() != secret.lower():
                #访问授权失败
                self.send_response(302)
                self.send_header("Location", "/login.html")
                self.end_headers()
                return
            else:
                #访问授权成功
                timestamp = str(datetime.datetime.now().timestamp())
                signature = sha1((timestamp + secret).encode()).hexdigest()
                self.send_response(302)
                if ("islong" in datas) and datas["islong"][0]:
                    cookie_expires = datetime.datetime.fromtimestamp(2147483647, datetime.timezone.utc).strftime("%a, %d %b %Y %H:%M:%S GMT")
                    self.send_header("Set-Cookie", "timestamp=%s; expires=%s; path=/; HttpOnly" %(timestamp, cookie_expires))
                    self.send_header("Set-Cookie", "signature=%s; expires=%s; path=/; HttpOnly" %(signature, cookie_expires))
                else:
                    self.send_header("Set-Cookie", "timestamp=%s; path=/; HttpOnly" %(timestamp,))
                    self.send_header("Set-Cookie", "signature=%s; path=/; HttpOnly" %(signature,))
                self.send_header("Location", "/")
                self.end_headers()
                return

        #剪贴板处理
        if path == "/api/clipoard":
            self.send_response(200)
            self.send_header("Content-type", "application/json; charset=utf-8")
            self.end_headers()
            #判断权限
            cookie = cookies.SimpleCookie()
            if self.headers["Cookie"] :
                cookie.load(self.headers["Cookie"])
            if ("timestamp" not in cookie) or ("signature" not in cookie) or sha1((cookie['timestamp'].value + secret).encode()).hexdigest() != cookie['signature'].value.lower():
                return self.post_failure(2)
            #业务逻辑
            return self.post_action(datas)


if __name__ == '__main__':
    httpd = HTTPServer(host, RequestHandler)
    if enableHttps:
        context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
        context.load_cert_chain('/cert/clipboard.crt', '/cert/clipboard.key')
        httpd.socket = context.wrap_socket(httpd.socket, server_side=True)
    print("Starting server, listen at: %s:%s, %s" % (host + ("with SSL" if enableHttps else "without SSL",)))
    httpd.serve_forever()
